package com.example.lotterysystem.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.lotterysystem.common.enums.*;
import com.example.lotterysystem.common.exception.GlobalException;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.common.utils.RedisUtil;
import com.example.lotterysystem.controller.param.ActivityCreateParam;
import com.example.lotterysystem.controller.param.CreatePrizeParam;
import com.example.lotterysystem.controller.param.PageParam;
import com.example.lotterysystem.dao.dataobject.*;
import com.example.lotterysystem.mapper.*;
import com.example.lotterysystem.service.ActivityService;
import com.example.lotterysystem.service.dto.ActivityCreateDTO;
import com.example.lotterysystem.service.dto.ActivityDTO;
import com.example.lotterysystem.service.dto.ActivityDetailDTO;
import com.example.lotterysystem.service.dto.PageListDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ActivityServiceImpl implements ActivityService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PrizeMapper prizeMapper;

    @Autowired
    private ActivityMapper activityMapper;

    @Autowired
    private ActivityUserMapper activityUserMapper;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private RedisUtil redisUtil;

    private final String ACTIVITY_ = "activity_";

    private final Long TTL = 60 * 60 * 24 * 3L;
    @Override
    @Transactional(rollbackFor = Exception.class) // 涉及多表
    public ActivityCreateDTO createActivity(ActivityCreateParam param) {
        // 校验活动信息是否正确
        checkActivityInfo(param);

        // 保存活动信息到数据库
        Activity activity = new Activity();
        activity.setActivityName(param.getActivityName());
        activity.setDescription(param.getDescription());
        activity.setStatus(ActivityStatusEnum.RUNNING.name());
        activityMapper.insert(activity);

        // 保存活动关联的人员信息
        List<ActivityCreateParam.ActivityUserParam> userList =
                param.getActivityUserList();
        List<ActivityUser> activityUserList = new ArrayList<>();
        for (ActivityCreateParam.ActivityUserParam user: userList) {
            ActivityUser activityUser = new ActivityUser();
            activityUser.setActivityId(activity.getId());
            activityUser.setUserId(user.getUserId());
            activityUser.setUserName(user.getUserName());
            activityUser.setStatus(ActivityUserStatusEnum.INIT.name());
            activityUserList.add(activityUser);
        }
        activityUserMapper.batchInsert(activityUserList);

        // 保存活动关联的奖品信息
        List<ActivityCreateParam.ActivityPrizeParam> prizeList =
                param.getActivityPrizeList();
        List<ActivityPrize> activityPrizeList = new ArrayList<>();
        for (ActivityCreateParam.ActivityPrizeParam prize: prizeList) {
            ActivityPrize activityPrize = new ActivityPrize();
            activityPrize.setActivityId(activity.getId());
            activityPrize.setPrizeId(prize.getPrizeId());
            activityPrize.setPrizeAmount(prize.getPrizeAmount());
            activityPrize.setPrizeTiers(prize.getPrizeTiers());
            activityPrize.setStatus(ActivityPrizeStatusEnum.INIT.name());
            activityPrizeList.add(activityPrize);
        }
        activityPrizeMapper.batchInsert(activityPrizeList);

        // 整合完整信息, 保存到 Redis 中
        // ActivityDetailDTO 活动信息+奖品信息+人员信息  key: activityId
        List<Long> prizeIds = param.getActivityPrizeList().stream()
                .map(ActivityCreateParam.ActivityPrizeParam::getPrizeId).toList();
        List<Prize> list = prizeMapper.selectList(new QueryWrapper<Prize>()
                .in("id", prizeIds));
        ActivityDetailDTO activityDetailDTO =
                transfer(activity, list, activityUserList, activityPrizeList);
        cacheActivityInfo(activityDetailDTO);

        // 构造返回
        ActivityCreateDTO createDTO = new ActivityCreateDTO();
        createDTO.setActivityId(activity.getId());
        return createDTO;
    }

    @Override
    public PageListDTO<ActivityDTO> getActivityList(PageParam param) {
        // 获取总量
        int total = activityMapper.selectCount(new QueryWrapper<>()).intValue();
        List<Activity> activities = activityMapper.selectActivityList(
                param.getOffset(), param.getPageSize());
        List<ActivityDTO> activityDTOList = activities.stream()
                .map(activity -> {
                    ActivityDTO activityDTO = new ActivityDTO();
                    activityDTO.setActivityId(activity.getId());
                    activityDTO.setActivityName(activity.getActivityName());
                    activityDTO.setDescription(activity.getDescription());
                    activityDTO.setStatus(ActivityStatusEnum.forName(activity.getStatus()));
                    return activityDTO;
                }).toList();
        return new PageListDTO<>(total, activityDTOList);
    }

    @Override
    public ActivityDetailDTO getActivityDetail(Long activityId) {
        if(activityId == null){
            log.warn("查询活动信息失败 activity 为 null");
        }
        ActivityDetailDTO activityInfoCache = getActivityInfoCache(activityId);
        log.info("activityInfoCache: {}", JacksonUtil.writeValueAsString(activityInfoCache));
        // 如果 redis 中存在相应的活动详细信息, 则直接返回
        if(null != activityInfoCache){
            return activityInfoCache;
        }
        // 不存在则在数据库中查询, 并整合到 redis 中
        activityInfoCache = cacheActivity(activityId);
        return activityInfoCache;
    }

    /**
     * 将抽奖结果缓存到 redis 中
     * @param activityId
     */
    @Override
    public ActivityDetailDTO cacheActivity(Long activityId) {
        if(activityId == null){
            log.warn("要缓存的活动 id 不存在");
            throw new GlobalException(GlobalErrorCodeEnum.CACHED_ERROR);
        }
        // 查询表数据并整合: 活动表, 关联奖品表, 关联人员表, 奖品信息表

        // 查询活动表
        Activity activity = activityMapper.selectOne(
                new QueryWrapper<Activity>().eq("id", activityId));
        if(activity == null){
            log.error("要缓存的活动 id 错误");
            throw new GlobalException(GlobalErrorCodeEnum.CACHED_ERROR);
        }
        // 查询奖品关联表
        List<ActivityPrize> prize = activityPrizeMapper.selectList(
                new QueryWrapper<ActivityPrize>().eq("activity_id", activityId));

        // 查询人员关联表
        List<ActivityUser> user = activityUserMapper.selectList(
                new QueryWrapper<ActivityUser>().eq("activity_id", activityId));

        List<Long> prizeIds = prize.stream().map(ActivityPrize::getPrizeId).toList();

        // 根据奖品 id 查询对应奖品信息表
        List<Prize> prizeList = prizeMapper.selectList(
                new QueryWrapper<Prize>().in("id", prizeIds));

        ActivityDetailDTO transfer = transfer(activity, prizeList, user, prize);
        cacheActivityInfo(transfer);
        return transfer;
    }

    private ActivityDetailDTO transfer(Activity activity, List<Prize> list,
                 List<ActivityUser> userList,
                 List<ActivityPrize> prizeList) {
        ActivityDetailDTO detailDTO = new ActivityDetailDTO();
        detailDTO.setActivityId(activity.getId());
        detailDTO.setActivityName(activity.getActivityName());
        detailDTO.setDescription(activity.getDescription());
        detailDTO.setActivityStatus(ActivityStatusEnum.forName(activity.getStatus()));

        List<ActivityDetailDTO.PrizeDTO> prizeDTOList = prizeList.stream()
            .map(apDO -> {
                ActivityDetailDTO.PrizeDTO prizeDTO = new ActivityDetailDTO.PrizeDTO();
                prizeDTO.setPrizeId(apDO.getPrizeId());
                Optional<Prize> first = list.stream().filter(
                        prize -> prize.getId().equals(apDO.getPrizeId())).findFirst();
                first.ifPresent(prize -> {
                    prizeDTO.setPrizeName(prize.getName());
                    prizeDTO.setImageUrl(prize.getImageUrl());
                    prizeDTO.setPrice(prize.getPrice());
                    prizeDTO.setDescription(prize.getDescription());
                });
                prizeDTO.setPrizeAmount(apDO.getPrizeAmount());
                prizeDTO.setPrizeGrade(PrizeGradeEnum.forName(apDO.getPrizeTiers()));
                prizeDTO.setPrizeStatus(ActivityPrizeStatusEnum.forName(apDO.getStatus()));
                return prizeDTO;
            }).toList();
        detailDTO.setPrizeList(prizeDTOList);
        List<ActivityDetailDTO.UserDTO> userDTOList = userList.stream()
            .map(auDO -> {
                ActivityDetailDTO.UserDTO userDTO = new ActivityDetailDTO.UserDTO();
                userDTO.setUserId(auDO.getUserId());
                userDTO.setUserName(auDO.getUserName());
                userDTO.setUserStatus(ActivityUserStatusEnum.forName(auDO.getStatus()));
                return userDTO;
            }).toList();
        detailDTO.setUserList(userDTOList);
        return detailDTO;
    }

    private void cacheActivityInfo(ActivityDetailDTO detailDTO) {
        // key: activityId
        // value: detailDTO
        if(detailDTO == null || detailDTO.getActivityId() == null){
            log.error("需要缓存的活动信息不存在");
            return;
        }
        try {
            redisUtil.set(ACTIVITY_ + detailDTO.getActivityId(),
                    JacksonUtil.writeValueAsString(detailDTO), TTL);
        } catch (Exception e){
            log.error("缓存活动出错 cacheActivityInfo: key: {}, value: {}", ACTIVITY_ + detailDTO.getActivityId(),
                    JacksonUtil.writeValueAsString(detailDTO), e);
        }
    }

    private ActivityDetailDTO getActivityInfoCache(Long activityId){
        if(activityId == null){
            log.warn("缓存的活动数据为空");
        }
        try {
            String detailDTO = redisUtil.get(ACTIVITY_ + activityId);
            if(!StringUtils.hasText(detailDTO)){
                log.warn("获取的缓存数据为空 key: {}", ACTIVITY_ + activityId);
                return null;
            }
            return JacksonUtil.readValue(detailDTO, ActivityDetailDTO.class);
        } catch (Exception e){
            log.error("获取活动信息失败 key: {}", ACTIVITY_ + activityId, e);
            return null;
        }
    }

    private void checkActivityInfo(ActivityCreateParam param) {
        if(param == null){
            throw new GlobalException(GlobalErrorCodeEnum.ACTIVITY_CREATE_ERROR);
        }

        // 校验活动名是否已经存在
        if(activityMapper.selectOne(new QueryWrapper<Activity>().eq("activity_name", param.getActivityName())) != null ){
            throw new GlobalException(GlobalErrorCodeEnum.ACTIVITY_CREATE_ERROR, "活动名已经存在");
        }

        // 人员 id 在人员表中存在
        // 1. 获取参数中的人员 id 并去重
        List<Long> userIds = param.getActivityUserList().stream()
                .map(ActivityCreateParam.ActivityUserParam::getUserId)
                .distinct().toList();

        // 2. 查在数据库表中存在的 参数 id
        List<User> userList = userMapper.selectList(new QueryWrapper<User>().in("id", userIds));
        List<Long> existsUserIds = userList.stream().map(User::getId).toList();
        if(CollectionUtils.isEmpty(existsUserIds)){
            throw new GlobalException(GlobalErrorCodeEnum.ACTIVITY_CREATE_ERROR, "活动人员 id 异常");
        }

        // 3. 判断参数中的 id, 若某个不在数据库表中 id 存在, 则抛异常
        userIds.forEach(id -> {
            if(!existsUserIds.contains(id)){
                throw new GlobalException(GlobalErrorCodeEnum.ACTIVITY_CREATE_ERROR, "活动人员 id 异常");
            }
        });


        // 奖品 id 在奖品表中存在
        // 1. 获取参数中的奖品 id 并去重
        List<Long> prizeIds = param.getActivityPrizeList().stream()
                .map(ActivityCreateParam.ActivityPrizeParam::getPrizeId)
                .distinct().toList();

        // 2. 查在数据库表中存在的 参数 id
        List<Prize> prizeList = prizeMapper.selectList(new QueryWrapper<Prize>().in("id", prizeIds));
        List<Long> existsPrizeIds = prizeList.stream().map(Prize::getId).toList();
        if(CollectionUtils.isEmpty(existsPrizeIds)){
            throw new GlobalException(GlobalErrorCodeEnum.ACTIVITY_CREATE_ERROR, "活动奖品 id 异常");
        }

        // 3. 判断参数中的 id, 若某个不在数据库表中 id 存在, 则抛异常
        prizeIds.forEach(id -> {
            if(!existsPrizeIds.contains(id)){
                throw new GlobalException(GlobalErrorCodeEnum.ACTIVITY_CREATE_ERROR, "活动奖品 id 异常");
            }
        });

        // 人员数量 >= 奖品数量
        int userAmount = param.getActivityUserList().size();
        long prizeAmount = param.getActivityPrizeList().stream()
                .mapToLong(ActivityCreateParam.ActivityPrizeParam::getPrizeAmount)
                .sum();
        if(userAmount < prizeAmount){
            throw new GlobalException(GlobalErrorCodeEnum.PRIZE_CREATE_ERROR, "人员数量小于奖品总数");
        }

        // 判断奖品等级是否规范
        param.getActivityPrizeList().forEach(prize -> {
            if(PrizeGradeEnum.forName(prize.getPrizeTiers()) == null){
                throw new GlobalException(GlobalErrorCodeEnum.PRIZE_CREATE_ERROR, "奖品等级异常");
            }
        });
    }
}
